﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Globalization;

namespace Hallenberg.SmartClient.Menu
{
    /// <summary>
    /// A collection of MenuManager instances.
    /// </summary>
    internal class MenuManager : IMenuManager
    {
        /// <summary>
        /// The internal list of registered menu managers.
        /// </summary>
        private List<MenuItemContainer> m_MenuManagers = new List<MenuItemContainer>();

        /// <summary>
        /// Cache containing the list of selected items for a given menu item name.
        /// </summary>
        private Dictionary<string, ReadOnlyCollection<MenuItem>> m_SelectedMenuItems = new Dictionary<string, ReadOnlyCollection<MenuItem>>();

        /// <summary>
        /// Returns the instance of the fallback menu manager.
        /// </summary>
        public MenuItemContainer FallbackMenuManager { get; private set; }

        /// <summary>
        /// Creates the specified menu manager.
        /// </summary>
        /// <param name="menuName">Name of the menu manager.</param>
        /// <returns>The new instance of a menu manager.</returns>
        public MenuItemContainer Create(string menuName)
        {
            Debug.Assert(string.IsNullOrEmpty(menuName) == false);
            if (string.IsNullOrEmpty(menuName))
            {
                throw new ArgumentNullException("menuName");
            }

            MenuItemContainer menuManager = new MenuItemContainer(menuName);

            m_MenuManagers.Add(menuManager);

            return menuManager;
        }

        /// <summary>
        /// Returns all menu managers.
        /// </summary>
        /// <returns>A list that might be empty. Never null.</returns>
        public ReadOnlyCollection<MenuItemContainer> AllMenuItemContainers
        {
            get
            {
                return new ReadOnlyCollection<MenuItemContainer>(m_MenuManagers);
            }
        }

        /// <summary>
        /// Finds the specified menu manager.
        /// </summary>
        /// <param name="menuName">Name of the menu.</param>
        /// <param name="create">Create the menu manager if it does not exist.</param>
        /// <returns>The found menu manager or null (not found)</returns>
        public MenuItemContainer Find(string menuName, bool create)
        {
            Debug.Assert(string.IsNullOrEmpty(menuName) == false);
            if (string.IsNullOrEmpty(menuName))
            {
                throw new ArgumentNullException("menuName");
            }

            MenuItemContainer foundMenuManager = m_MenuManagers.FirstOrDefault(mm => mm.MenuItemTitle == menuName);
            if (foundMenuManager == null && create)
            {
                foundMenuManager = Create(menuName);
            }

            if (foundMenuManager == null && FallbackMenuManager == null)
            {
                throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Could not locate menu [{0}] and fallback menu manager has not been set", menuName));
            }

            if (foundMenuManager == null)
            {
                return FallbackMenuManager;
            }

            return foundMenuManager;
        }

        /// <summary>
        /// If a menu manager cannot be found using find, this instance is
        /// returned as default instead. The MenuManager does not have to
        /// be a part of the MenuManagerCollection.
        /// </summary>
        /// <param name="menuManager">The fallback menu manager</param>
        public void SetFallbackMenuManager(MenuItemContainer menuManager)
        {
            FallbackMenuManager = menuManager;
        }

        /// <summary>
        /// Returns a collection of the items selected when the provided
        /// menu item (menuItemName) is the action being invoked.
        /// </summary>
        /// <param name="menuItemName"></param>
        public ReadOnlyCollection<MenuItem> GetSelectedMenuItems(string menuCollectionName, string menuItemName)
        {
            Debug.Assert(string.IsNullOrEmpty(menuItemName) == false);
            if (string.IsNullOrEmpty(menuItemName))
            {
                throw new ArgumentNullException("menuItemName");
            }

            Debug.Assert(string.IsNullOrEmpty(menuCollectionName) == false);
            if (string.IsNullOrEmpty(menuCollectionName))
            {
                throw new ArgumentNullException("menuCollectionName");
            }

            if (m_SelectedMenuItems.ContainsKey(menuItemName))
            {
                return m_SelectedMenuItems[menuItemName];
            }

            List<MenuItem> selectedMenuItems = new List<MenuItem>();

            MenuItemContainer currentMenuManager = Find(menuCollectionName, false);
            MenuItem menuItem = currentMenuManager.ChildItems.FirstOrDefault(menu => menu.MenuItemName == menuItemName);
            
            while (menuItem != null)
            {
                selectedMenuItems.Add(menuItem);
                
                MenuItemContainer menuManager = m_MenuManagers.FirstOrDefault(mm => mm.ChildItems.Any(ci => ci.MenuItemName == currentMenuManager.MenuItemTitle));
                if (menuManager != null)
                {
                    menuItem = menuManager.ChildItems.FirstOrDefault(ci => ci.MenuItemName == currentMenuManager.MenuItemTitle);
                    currentMenuManager = menuManager;
                }
                else
                {
                    menuItem = null;
                }
            }

            m_SelectedMenuItems.Add(menuItemName, new ReadOnlyCollection<MenuItem>(selectedMenuItems));

            return m_SelectedMenuItems[menuItemName];
        }
    }
}
